// miniloop, a simple live looper
//
// Copyright (C) 2007 Nick Thomas
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of the <organization> nor the
//       names of its contributors may be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY NICK THOMAS ``AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL NICK THOMAS BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include <stdio.h>
#include <stdlib.h>
#include <sndfile.h>
#include "Config.hpp"
#include "Engine.hpp"
using namespace miniloop;

Engine::Engine()
{
    m_jack_initialized = false;
    m_seq_initialized = false;
}

bool Engine::init(char *&error)
{
    bool err = false;

    m_audio_ports.resize(g_config.num_loops);

    m_loop_data = new float[g_config.num_loops * 2 * g_config.loop_length];
    m_loop_frame = 0;

    // Initialize SRC.
    int src_errnum;
    m_last_received_pitch = m_src_pitch = 64;
    m_pitch_is_held = false;

    m_src_state = src_callback_new(&Engine::src_callback,
	    g_config.src_converter_type, g_config.num_loops * 2,
	    &src_errnum, this);

    if (!m_src_state) {
	error = (char *)src_strerror(src_errnum);
	err = true;
    }

    // Initialize ALSAseq.
    if (!err) {
	int errnum = snd_seq_open(&m_seq, "default", SND_SEQ_OPEN_INPUT,
		SND_SEQ_NONBLOCK);
	if (errnum < 0) {
	    error = (char *)snd_strerror(errnum);
	    err = true;
	}
    }

    if (!err) {
	snd_seq_set_client_name(m_seq, "miniloop");
	m_seq_id = snd_seq_client_id(m_seq);

	if (m_seq_id < 0) {
	    error = (char *)snd_strerror(m_seq_id);
	    snd_seq_close(m_seq);
	    err = true;
	}
    }

    if (!err) {
	int errnum = snd_seq_create_simple_port(m_seq, "miniloop",
		SND_SEQ_PORT_CAP_WRITE |
		SND_SEQ_PORT_CAP_SUBS_WRITE,
		SND_SEQ_PORT_TYPE_MIDI_GENERIC |
		SND_SEQ_PORT_TYPE_SOFTWARE |
		SND_SEQ_PORT_TYPE_SYNTHESIZER);

	if (errnum >= 0) {
	    m_seq_port = errnum;
	    m_seq_initialized = true;
	} else {
	    error = (char *)snd_strerror(errnum);
	    err = true;
	}
    }

    // Initialize JACK.
    if (!err) {
    	m_loop_load_ringbuf = jack_ringbuffer_create(sizeof(int) * 2);
    	m_raw_data = new float[g_config.loop_length * 2];
    	m_data_L = new float[g_config.loop_length];
    	m_data_R = new float[g_config.loop_length];

	jack_status_t jack_status;
    	m_jack_client = jack_client_open("miniloop",
		JackNullOption, &jack_status);

    	if (jack_status) {
            error = "Could not connect to JACK.";
            err = true;
	}
    }

    if (!err) {
	for (int i = 0; !err && i < g_config.num_loops; i++) {
	    char port_name[10];

	    sprintf(port_name, "loop_%dL", i + 1);
	    m_audio_ports[i].left =
                jack_port_register(m_jack_client, port_name,
		    JACK_DEFAULT_AUDIO_TYPE,
		    JackPortIsOutput | JackPortIsTerminal, 0);

	    sprintf(port_name, "loop_%dR", i + 1);
	    m_audio_ports[i].right =
		jack_port_register(m_jack_client, port_name,
			JACK_DEFAULT_AUDIO_TYPE,
			JackPortIsOutput | JackPortIsTerminal, 0);

	    if (!(m_audio_ports[i].right && m_audio_ports[i].left)) {
		error = "Could not register JACK audio outputs.";
		err = true;
	    }
	}
    }

    if (!err) {
        if (jack_set_process_callback(m_jack_client,
                                      &Engine::jack_processing_callback,
                                      this)) {
            error = "Could not register a JACK processing callback.";
            err = true;
        }
    }

    if (!err) {
	m_jack_temp_buffer = 0;

	if (jack_set_buffer_size_callback(m_jack_client,
		                          &Engine::jack_processing_callback,
					  this)) {
	    error = "Could not register a JACK buffer size callback.";
	    err = true;
	}
    }

    if (!err) {
	m_jack_temp_buffer = new float[g_config.num_loops * 2 *
	    jack_get_buffer_size(m_jack_client)];

        if (jack_activate(m_jack_client)) {
            error = "Could not activate JACK audio processing.";
            err = true;
        } else {
	    m_jack_initialized = true;
	}
    }

    return !err;
}

bool Engine::load_loop(int id, Glib::ustring filename, Glib::ustring &error)
{
    SF_INFO sfinfo;
    SNDFILE *sndfile;
    bool err = false;

    sndfile = sf_open(filename.c_str(), SFM_READ, &sfinfo);

    if (!sndfile) {
	err = true;
	error = "could not read the file";
    }

    if (!err) {
	if (sfinfo.frames - g_config.loop_length != 0) {
	    printf("%s: frame count error %d.\n", filename.c_str(),
		    (int)(sfinfo.frames - g_config.loop_length));
	}
	if (sfinfo.channels > 2) {
	    err = true;
	    error = "file has more than two channels";
	}
    }

    if (!err) {
	sf_readf_float(sndfile, m_raw_data, g_config.loop_length);

	if (sfinfo.channels == 1) {
	    // Mono
	    memcpy(m_data_L, m_raw_data, sizeof(float) * g_config.loop_length);
	    memcpy(m_data_R, m_raw_data, sizeof(float) * g_config.loop_length);
	} else {
	    // Stereo
	    for (int i = 0; i < g_config.loop_length; i++) {
		m_data_L[i] = m_raw_data[i*2];
		m_data_R[i] = m_raw_data[i*2+1];
	    }
	}

	jack_ringbuffer_write(m_loop_load_ringbuf, (const char *)&id,
		sizeof(int));
    }

    if (sndfile) {
	sf_close(sndfile);
    }

    return !err;
}

Engine::~Engine()
{
    if (m_jack_initialized) {
        jack_deactivate(m_jack_client);
        jack_client_close(m_jack_client);
    }

    if (m_seq_initialized) {
	snd_seq_delete_simple_port(m_seq, m_seq_port);
	snd_seq_close(m_seq);
    }
}

int Engine::jack_processing_callback(jack_nframes_t nframes, void *arg)
{
    return ((Engine *)arg)->jack_process(nframes);
}

int Engine::jack_buffer_size_callback(jack_nframes_t nframes, void *arg)
{
    Engine *engine = (Engine *)arg;

    if (engine->m_jack_temp_buffer) {
	delete engine->m_jack_temp_buffer;
    }

    engine->m_jack_temp_buffer = new float[g_config.num_loops * 2 * nframes];

    // XXX: No idea what to return here. Tentatively return an optimistic 1.
    return 1;
}

int Engine::jack_process(jack_nframes_t nframes)
{
    int frame_length = 2 * g_config.num_loops;
    seq_process();

    if (jack_ringbuffer_read_space(m_loop_load_ringbuf) > 0) {
	int id;
	jack_ringbuffer_read(m_loop_load_ringbuf, (char *)&id, sizeof(int));

	for (int i = 0; i < g_config.loop_length; i++) {
	    m_loop_data[i * frame_length + 2 * id] = m_data_L[i];
	    m_loop_data[i * frame_length + 2 * id + 1] = m_data_R[i];
	}
    }

    double src_ratio = 1.0 - ((double)m_src_pitch - 64) *
	(g_config.src_pitch_range / 64);

    src_callback_read(m_src_state, src_ratio, nframes, m_jack_temp_buffer);

    for (int loop = 0; loop < g_config.num_loops; loop++) {
	float *out_L =
	    (float *)jack_port_get_buffer(m_audio_ports[loop].left, nframes);
	float *out_R =
	    (float *)jack_port_get_buffer(m_audio_ports[loop].right, nframes);

	for (int sample = 0; sample < (int)nframes; sample++) {
	    out_L[sample] = m_jack_temp_buffer[sample * frame_length + 2 * loop];
	    out_R[sample] =
		m_jack_temp_buffer[sample * frame_length + 2 * loop + 1];
	}
    }

    m_loop_frame = (m_loop_frame + nframes) % g_config.loop_length;

    return 0;
}

void Engine::seq_process()
{
    int errnum;
    snd_seq_event_t *event;

    while (1) {
	errnum = snd_seq_event_input(m_seq, &event);

	if (errnum < 0) {
	    break;
	}

	if (event->type == SND_SEQ_EVENT_CONTROLLER &&
	    (g_config.midi_channel == 0 ? true :
	     event->data.control.channel == g_config.midi_channel - 1)) {
	    if ((int)event->data.control.param == g_config.midi_pitch_control) {
		m_last_received_pitch = event->data.control.value;
		if (!m_pitch_is_held) {
		    m_src_pitch = event->data.control.value;
		}
	    } else if ((int)event->data.control.param ==
		    g_config.midi_hold_control) {
		if (event->data.control.value >= 64) {
		    m_pitch_is_held = true;
		} else {
		    m_pitch_is_held = false;
		    m_src_pitch = m_last_received_pitch;
		}
	    }
	}
    }

    return;
}

long Engine::src_callback(void *cb_data, float **data)
{
    *data = ((Engine *)cb_data)->m_loop_data;
    return g_config.loop_length;
}
